package optimization.naturalOptimization.evolutionaryAlgorithm.evolutionStrategy;

import java.util.ArrayList;
import java.util.Arrays;

import optimization.naturalOptimization.NaturalOptimization;
import optimization.naturalOptimization.evolutionaryAlgorithm.evolutionStrategy.cma.CMAEvolutionStrategy;
import optimization.naturalOptimization.evolutionaryAlgorithm.evolutionStrategy.cma.IntDouble;
import optimization.naturalOptimization.population.individual.EA.DoubleArrayEAIndividual;

/**
 * Implementation of the CMA ES using the implementation of Nikolaus Hannsen.
 * 
 * @author Kevin Wagner
 * @version 1.0
 */
public class MuCommaLambdaCMAES extends EvolutionStrategy {

	private CMAEvolutionStrategy cma;
	private double[] fitness;
	private double[][] pop;

	@Override
	public boolean init() {
		setRng();
		if (!isDimensionBasedPopulation()) {
			cma = new CMAEvolutionStrategy(getDimensionality(), getLambda());
		} else {
			cma = new CMAEvolutionStrategy(getDimensionality());
		}
		cma.setParentOptimization(this);
		cma.setRNG(getRng());
		cma.setInitialStandardDeviation(getInitialStd());
		getProperties().setProperty("mu", cma.getParameters().getMu() + "");
		getProperties().setProperty("lambda",
				cma.getParameters().getLambda() + "");
		super.init();
		if (getBoundaries() == null) {
			cma.setLBound(null);
			cma.setUBound(null);
		} else {
			cma.setLBound((double[]) getSearchSpace().getLowerBounds());
			cma.setUBound((double[]) getSearchSpace().getUpperBounds());
		}
		// Andieser stelle, weil erst die Natural Optimization methode
		// aufgerufen werden muss, um den Suchraum zu initialisierunen.
		cma.setInitialX((double[]) getSearchSpace().createRandomPosition());
		fitness = cma.init();
		pop = cma.samplePopulation();
		for (int i = 0; i < getLambda(); i++) {
			DoubleArrayEAIndividual ind = new DoubleArrayEAIndividual(
					getProgenyPopulation());
			ind.setPosition(pop[i]);
			getProgenyPopulation().addIndividual(ind);
		}
		try {
			getFitness().getFitness(getProgenyPopulation());
		} catch (Exception e) {
			pushError(e);
			return false;
		}
		IntDouble[] ind = new IntDouble[getLambda()];
		for (int i = 0; i < getLambda(); i++) {
			fitness[i] = getProgenyPopulation().getIndividual(i)
					.getPositionFitness();
			ind[i] = new IntDouble(getProgenyPopulation().getIndividual(i)
					.getPositionFitness(), i);
		}
		sortIndividuals(ind);
		cma.updateDistribution(fitness);
		for (int i = 0; i < getMu(); i++) {
			int temp = ind[i].getIndex();
			DoubleArrayEAIndividual newInd = new DoubleArrayEAIndividual(
					getParentPopulation());
			newInd.setPositionFitness(ind[i].getValue());
			newInd.setPosition((double[]) getProgenyPopulation().getIndividual(
					temp).getPosition());
			getParentPopulation().addIndividual(newInd);
		}
		getProgenyPopulation().removeAllIndividuals();
		setResult(getCurrentBestFitness());
		exportIndividualData();
		setStatus(NaturalOptimization.inizialized);
		return true;
	}

	/**
	 * Bubblesort implementation to sort individuals,
	 * 
	 * @param ind
	 *            list of individuals.
	 */
	private void sortIndividuals(IntDouble[] ind) {
		boolean unsorted = true;
		IntDouble temp;
		while (unsorted) {
			unsorted = false;
			for (int i = 0; i < ind.length - 1; i++) {
				if (getFitness().compare(ind[i].getValue(),
						ind[i + 1].getValue()) == 2) {
					temp = ind[i];
					ind[i] = ind[i + 1];
					ind[i + 1] = temp;
				}
			}
		}

	}

	/**
	 * 
	 * @return {@code TRUE}, if the population size is based on dimensionality,
	 *         {@code FALSE}, if value lambda is set by property file.
	 */
	private boolean isDimensionBasedPopulation() {
		return Boolean.valueOf(getProperties().getProperty(
				"dimensionBasedPopulation", "true"));
	}

	@Override
	public String getIndentification() {
		return "Mu Comma Lambda CMAES";
	}

	@Override
	public boolean performStep() {
		getParentPopulation().removeAllIndividuals();
		pop = cma.samplePopulation();
		for (int i = 0; i < getLambda(); i++) {
			DoubleArrayEAIndividual ind = new DoubleArrayEAIndividual(
					getProgenyPopulation());
			ind.setPosition(pop[i]);
			getProgenyPopulation().addIndividual(ind);
		}
		try {
			getFitness().getFitness(getProgenyPopulation());
		} catch (Exception e) {
			pushError(e);
			return false;
		}
		for (int i = 0; i < getLambda(); i++) {
			fitness[i] = getProgenyPopulation().getIndividual(i)
					.getPositionFitness();
		}
		IntDouble[] ind = new IntDouble[getLambda()];
		for (int i = 0; i < getLambda(); i++) {
			fitness[i] = getProgenyPopulation().getIndividual(i)
					.getPositionFitness();
			ind[i] = new IntDouble(getProgenyPopulation().getIndividual(i)
					.getPositionFitness(), i);
		}
		Arrays.sort(ind, ind[0]);
		cma.updateDistribution(fitness);
		for (int i = 0; i < getMu(); i++) {
			int temp = ind[i].getIndex();
			DoubleArrayEAIndividual newInd = new DoubleArrayEAIndividual(
					getParentPopulation());
			newInd.setPositionFitness(ind[i].getValue());
			newInd.setPosition((double[]) getProgenyPopulation().getIndividual(
					temp).getPosition());
			getParentPopulation().addIndividual(newInd);
		}
		getProgenyPopulation().removeAllIndividuals();
		return super.performStep();
	}

	/**
	 * Returns the initial standard deviation
	 * 
	 * @return initial std
	 */
	public double getInitialStd() {
		return Double.valueOf(getProperties().getProperty("initialStd", "0.5"));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see optimization.naturalOptimization.NaturalOptimization#getParameters()
	 */
	public ArrayList<String> getParameters() {
		ArrayList<String> parameters = super.getParameters();
		parameters.add("Initial Standard Deviation");
		return parameters;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see optimization.naturalOptimization.NaturalOptimization#
	 * getParameterConfiguration()
	 */
	public ArrayList<String> getParameterConfiguration() {
		ArrayList<String> configuration = super.getParameterConfiguration();
		configuration.add(getInitialStd() + "");
		return configuration;
	}

}
